#include <Arduino.h>
#include <U8x8lib.h>
#define _int uint8_t

#define _EMPTY 0
#define _APPLE 1
#define _BODY 2
#define _HEAD 3

U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE);

_int mp[8][128]; // size: 64 * 128

void flush() {
  for (_int i = 0; !(i & 8); i ++)
    u8x8.drawTile(0, i, 16, mp[i]);
}

void setPixel(_int x, _int y, bool t) {
  if (x & 64 | y & 128) return;
  if (t) mp[x >> 3][y] |= 1 << (x & 7);
  else mp[x >> 3][y] &= 0xff ^ (1 << (x & 7));
}

bool getPixel(_int x, _int y) {
  if (x & 64 | y & 128) return 0;
  return mp[x >> 3][y] & (1 << (x & 7));
}

void _init(){
  u8x8.begin();
  u8x8.setFlipMode(1);
}

_int Map[256]; // 0: empty  1: toLeft  2: toUp
               // 3: toRight  4: toDown  5: apple

// MapSize: 16 * 32

void setBlock(_int x, _int y, _int t){
  if (x & 16 | y & 32) return;
  if (y & 1){
    Map[x << 4 | y >> 1] &= 0x0f;
    Map[x << 4 | y >> 1] |= t << 4;
  } else {
    Map[x << 4 | y >> 1] &= 0xf0;
    Map[x << 4 | y >> 1] |= t;
  }
}

_int getBlock(_int x, _int y){
  if (x & 16 | y & 32) return 0;
  if (y & 1) return Map[x << 4 | y >> 1] >> 4;
  else return Map[x << 4 | y >> 1] & 15;
}

void newApple(){
  int x = rand() & 15;
  int y = rand() & 31;
  while (getBlock(x, y)){
    x = rand() & 15;
    y = rand() & 31;
  }
  setBlock(x, y, 5);
}

_int dire; // 0: Down  1: Left  2: Up  3: Right
_int headPosX, headPosY;
_int tailPosX, tailPosY;

void gameInit(){
  dire = 3;
  headPosX = tailPosX = tailPosY = 1;
  headPosY = 3;

  memset(Map, 0, sizeof(Map));
  setBlock(1, 1, 3);
  setBlock(1, 2, 3);
  setBlock(1, 3, 3); // prevent apple in the head
  newApple();
}

void plot(_int x, _int y, _int t){
  _int b1, b2;
  if (t == _EMPTY) b1 = 0x00, b2 = 0x00;
  if (t == _APPLE) b1 = 0x06, b2 = 0x60;
  if (t == _BODY)  b1 = 0x6f, b2 = 0xf6;
  if (t == _HEAD)  b1 = 0x69, b2 = 0x96;
  for (_int i = 0; i < 16; i ++)
    setPixel(x << 2 | i >> 2, y << 2 | i & 3, ((i & 8) ? b1 : b2) & (1 << (i & 7)));
}

void paint(){
  for (_int i = 0; i < 16; i ++)
    for (_int j = 0; j < 32; j ++){
      _int tmp = getBlock(i, j);
      if (tmp == 0) {plot(i, j, _EMPTY); continue;}
      if (tmp == 5) {plot(i, j, _APPLE); continue;}
      plot(i, j, _BODY);
    }
  plot(headPosX, headPosY, _HEAD);
  flush();
}

void gameOver(){
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.clear();
  u8x8.setCursor(0, 0);
  u8x8.print("Game Over!");
  for(;;);
}

void go(_int &x, _int &y, _int dire){
  if (dire == 0 || dire == 4) x ++;
  if (dire == 1) y --;
  if (dire == 2) x --;
  if (dire == 3) y ++;
}

bool isOut(_int x, _int y){
  return (bool)(x & 16 | y & 32);
}

void setup(){
  _init();
  srand(millis());
  gameInit();
  pinMode(A0, INPUT);
}

void loop(){
  
  int op = map(analogRead(A0), 0, 1024, 0, 4); // 0: turn left
                                           // 12: go straight
                                           // 3: turn right
  if (op == 0) dire = (dire + 1) % 4;
  if (op == 3) dire = (dire + 3) % 4;

  _int tmpx, tmpy;
  tmpx = headPosX, tmpy = headPosY;
  go(tmpx, tmpy, dire);
  if (isOut(tmpx, tmpy)) gameOver();
  bool eat = getBlock(tmpx, tmpy) == 5;
  
  if (!eat){
    _int d = getBlock(tailPosX, tailPosY);
    setBlock(tailPosX, tailPosY ,0);
    go(tailPosX, tailPosY, d);
  } else
    newApple();

  if (getBlock(tmpx, tmpy) != 0 && getBlock(tmpx, tmpy) != 5) gameOver();
  setBlock(headPosX, headPosY, (dire + 3) % 4 + 1);
  headPosX = tmpx, headPosY = tmpy;
  setBlock(tmpx, tmpy, 1); // prevent apple in the head

  paint();
  delay(200);
}